"""
MQTT Client Program - Multi-topic Publisher Version
Features: 
- Subscribe to multiple topics 
- Publish JSON messages to multiple topics with configurable intervals
- Configurable delay between publishing to different topics
Author: AI Assistant
Version: 1.3
Updates:
1. Added support for delay between publishing to different topics
2. Separate interval between messages and interval between topics
3. Enhanced user configuration
"""

import paho.mqtt.client as mqtt
import threading
import time
import json

class MQTTClient:
    def __init__(self):
        """
        Initialize MQTT client
        - Uses MQTT callback API v2
        - Sets subscription/publish topic lists
        - Initializes state flags
        """
        # Use v2 API to avoid deprecation warnings
        self.client = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
        
        # Bind callback functions
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        
        # Thread control variables
        self.publish_timer = None
        self.running = False
        
        # List of topics to subscribe to (modify as needed)
        self.subscribe_topics = [
            "ppjia0",  # Subscription topic 1
            "ppjia1",  # Subscription topic 2
            "ppjia2",   # Subscription topic 3
            "ppjia4"   # Subscription topic 3
        ]
        
        # List of topics to publish to (modify as needed)
        self.publish_topics = [
            "ddjia0",  # Publish topic 1
            "ddjia1",  # Publish topic 2
            "ddjia2",  # Publish topic 3
            "ddjia3"   # Publish topic 4
        ]
        
        # Fixed JSON message template (modify as needed)
        self.message_template = {
            "type": 1,                    # Message type identifier
            "value": "010200010002cff2",  # Example data value
            "arg": 12144                  # Example parameter
        }

    def on_connect(self, client, userdata, flags, rc, properties=None):
        """
        Connection callback
        - rc=0 indicates successful connection
        - Automatically subscribes to predefined topics on success
        - Starts publishing thread
        """
        print(f"Connection result code: {rc} (0 means success)")
        if rc == 0:
            # Subscribe to all predefined topics
            for topic in self.subscribe_topics:
                client.subscribe(topic)
                print(f"Subscribed to topic: {topic}")
            
            # Start timed publishing
            self.start_publishing()
        else:
            print("Connection failed! Check network and broker settings")

    def on_message(self, client, userdata, msg):
        """
        Message reception callback
        - Prints received message content and topic
        - Includes basic error handling
        """
        try:
            payload = msg.payload.decode('utf-8')
            print(f"Received message on [{msg.topic}]: {payload}")
        except UnicodeDecodeError:
            print(f"Message decode failed [Topic: {msg.topic}]")
        except Exception as e:
            print(f"Error processing message: {str(e)}")

    def publish_message(self):
        """
        Message publishing thread function
        - Periodically publishes JSON message to all publish topics
        - Maintains specified publish interval between messages
        - Waits specified delay between publishing to different topics
        - Includes error handling and recovery
        """
        while self.running:
            try:
                # Serialize JSON message
                message = json.dumps(self.message_template)
                
                # Publish to all topics with delay between them
                for topic in self.publish_topics:
                    if not self.running:  # Check if we should stop
                        break
                        
                    self.client.publish(
                        topic=topic,
                        payload=message,
                        qos=0,
                        retain=False
                    )
                    print(f"Published to [{topic}]: {message}")
                    
                    # Wait between publishing to different topics
                    if topic != self.publish_topics[-1]:  # Don't wait after last topic
                        time.sleep(self.topic_delay)
                
                # Wait between full cycles of publishing to all topics
                time.sleep(self.publish_interval)
                
            except json.JSONDecodeError:
                print("JSON serialization failed! Check message template")
                time.sleep(1)
            except Exception as e:
                print(f"Publish error: {str(e)}")
                time.sleep(1)  # Wait 1 second on error

    def start_publishing(self):
        """
        Start publishing thread
        - Sets running flag
        - Creates daemon thread
        - Prints startup info
        """
        self.running = True
        self.publish_timer = threading.Thread(
            target=self.publish_message,
            daemon=True  # Set as daemon thread
        )
        self.publish_timer.start()
        print(f"Started timed publishing:")
        print(f"- Message interval: {int(self.publish_interval*1000)}ms")
        print(f"- Topic delay: {int(self.topic_delay*1000)}ms")
        print(f"Publish topics: {', '.join(self.publish_topics)}")

    def stop_publishing(self):
        """
        Stop publishing thread
        - Clears running flag
        - Waits for thread to finish
        """
        self.running = False
        if self.publish_timer and self.publish_timer.is_alive():
            self.publish_timer.join(timeout=2)  # Max 2 second wait
        print("Stopped publishing")

    def connect(self, broker_address, broker_port, publish_interval_ms, topic_delay_ms):
        """
        Main broker connection function
        - Parameters:
          broker_address: Broker server address
          broker_port: Broker service port
          publish_interval_ms: Publish interval in milliseconds (between full cycles)
          topic_delay_ms: Delay between publishing to different topics in milliseconds
        """
        # Convert milliseconds to seconds (internal use)
        self.publish_interval = publish_interval_ms / 1000.0
        self.topic_delay = topic_delay_ms / 1000.0
        
        try:
            print(f"Connecting to server: {broker_address}:{broker_port}...")
            
            # Set 60 second connection timeout
            self.client.connect(
                host=broker_address,
                port=broker_port,
                keepalive=60
            )
            
            # Start network loop (blocking call)
            self.client.loop_forever()
            
        except ConnectionRefusedError:
            print("Connection refused! Please check:")
            print("1. Broker address and port")
            print("2. Broker service status")
            print("3. Firewall settings")
        except Exception as e:
            print(f"Connection error: {str(e)}")
        except KeyboardInterrupt:
            print("\nDisconnecting...")
            self.stop_publishing()
            self.client.disconnect()
            print("Disconnected")

if __name__ == "__main__":
    # Program entry point
    print("="*50)
    print("MQTT Client - Multi-topic Publisher")
    print("Features: Multi-topic Subscribe + Publish with configurable delays")
    print("="*50)
    print("""
    Usage Instructions
    -----------------------------
    1. Enter broker address and port when prompted
    2. Enter publish interval in milliseconds (between full cycles)
    3. Enter delay between publishing to different topics
    4. Press Ctrl+C to exit
    """)
    
    # Default configuration
    DEFAULT_BROKER = "broker.emqx.io"
    DEFAULT_PORT = 1883
    DEFAULT_INTERVAL_MS = 1000  # Default 1000ms between full cycles
    DEFAULT_TOPIC_DELAY_MS = 100  # Default 100ms between topics
    
    # Get user input
    broker_address = input(f"Enter broker address (default {DEFAULT_BROKER}): ").strip()
    broker_port = input(f"Enter port (default {DEFAULT_PORT}): ").strip()
    interval_input = input(f"Enter publish interval (ms, default {DEFAULT_INTERVAL_MS}): ").strip()
    topic_delay_input = input(f"Enter delay between topics (ms, default {DEFAULT_TOPIC_DELAY_MS}): ").strip()
    
    # Process input
    broker_address = broker_address or DEFAULT_BROKER
    try:
        broker_port = int(broker_port) if broker_port else DEFAULT_PORT
    except ValueError:
        print("Invalid port, using default")
        broker_port = DEFAULT_PORT
    
    try:
        publish_interval_ms = float(interval_input) if interval_input else DEFAULT_INTERVAL_MS
    except ValueError:
        print("Invalid interval, using default")
        publish_interval_ms = DEFAULT_INTERVAL_MS
    
    try:
        topic_delay_ms = float(topic_delay_input) if topic_delay_input else DEFAULT_TOPIC_DELAY_MS
    except ValueError:
        print("Invalid topic delay, using default")
        topic_delay_ms = DEFAULT_TOPIC_DELAY_MS
    
    # Create and start client
    client = MQTTClient()
    client.connect(broker_address, broker_port, publish_interval_ms, topic_delay_ms)